package com.lyy.mybatis.sensitive.utils;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.lyy.mybatis.sensitive.annotation.SensitiveField;
import com.lyy.mybatis.sensitive.annotation.SensitiveTable;
import com.lyy.mybatis.sensitive.secret.SecretAbstract;
import lombok.SneakyThrows;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;

import java.lang.reflect.Field;
import java.util.Objects;
import java.util.function.Function;

/**
 * 数据库字段加解密工具类
 */
public class EncryptDecryptUtil {

    /**
     * 单个实体对象加密
     * @param parameterObject
     * @return
     * @param <T>
     * @throws IllegalAccessException
     */
    public static <T> T encrypt(T parameterObject) throws IllegalAccessException {
        Class<?> parameterObjectClass = parameterObject.getClass();
        Field[] declaredFields = parameterObjectClass.getDeclaredFields();
        return encrypt(declaredFields, parameterObject);
    }

    /**
     * 加密字符串
     * @param str 要加密的字符串
     * @param secret 加密类型
     * @param expression 表达式类型
     * @return 加密后的字符串
     */
    @SneakyThrows
    public static String encryptString(String str, Class<? extends SecretAbstract> secret, Expression expression) {
        if (StrUtil.isBlank(str)){
            return str;
        }
        SecretAbstract secretAbstract = ReflectUtil.newInstance(secret);
        // like表达式需要去掉特定加密标识去查询
        if (expression instanceof LikeExpression) {
            // str 是否左边模糊匹配(判断左起第一个是否存在百分号)
            boolean isLeft = str.charAt(0) == '%';
            // str 是否右边模糊匹配(判断右起第一个是否存在百分号)
            boolean isRight = str.charAt(str.length() - 1) == '%';
            // str 是否完全匹配(判断字符串是否左右包含百分号)
            boolean isFull = isLeft && isRight;
            if (isFull) {
                String encryptWithOutFlag = secretAbstract.encryptWithOutFlag(str.substring(1, str.length() - 1));
                return StrUtil.format("%{}%", encryptWithOutFlag);
            } else if (isLeft) {
                String encryptWithOutFlag = secretAbstract.encryptWithOutFlag(str.substring(1));
                return StrUtil.format("%{}", encryptWithOutFlag);
            } else if (isRight) {
                String encryptWithOutFlag = secretAbstract.encryptWithOutFlag(str.substring(0, str.length() - 1));
                return StrUtil.format("{}%", encryptWithOutFlag);
            } else {
                return secretAbstract.encrypt(str);
            }
        }
        return secretAbstract.encrypt(str);
    }

    /**
     * 多field加密方法
     *
     * @param declaredFields 实体类声明的字段
     * @param parameterObject 实体类
     * @return
     * @throws IllegalAccessException
     */
    public static <T> T encrypt(Field[] declaredFields, T parameterObject) throws IllegalAccessException {
        for (Field field : declaredFields) {
            encrypt(field, parameterObject);
        }
        return parameterObject;
    }


    /**
     * 单个field加密方法
     *
     * @param field
     * @param parameterObject
     * @param <T>
     * @return
     */
    @SneakyThrows
    public static <T> T encrypt(Field field, T parameterObject) {
        field.setAccessible(true);
        SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
        if (Objects.nonNull(sensitiveField)) {
            Object object = field.get(parameterObject);
            Class<? extends SecretAbstract> secret = sensitiveField.value();
            fieldEncrypt(field, parameterObject, object, e -> EncryptDecryptUtil.encryptString(e, secret,null));
        }
        return parameterObject;
    }

    public static <T> void fieldEncrypt(Field field, T parameterObject, Object object,
                                        Function<String, String> function) {
        if (object instanceof String) {
            //定制String类型的加密算法
            String value = (String) object;
            try {
                field.set(parameterObject, function.apply(value));
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }


    /**
     * 解密方法
     *
     * @param result
     * @param <T>
     * @return
     * @throws IllegalAccessException
     */
    public static <T> T decrypt(T result) throws IllegalAccessException {
        Class<?> parameterObjectClass = result.getClass();
        Field[] declaredFields = parameterObjectClass.getDeclaredFields();
        decrypt(declaredFields, result);
        return result;
    }

    /**
     * 多个field解密方法
     *
     * @param declaredFields
     * @param result
     * @throws IllegalAccessException
     */
    public static void decrypt(Field[] declaredFields, Object result) throws IllegalAccessException {
        for (Field field : declaredFields) {
            decrypt(field, result);
        }
    }

    /**
     * 单个field解密方法
     *
     * @param field
     * @param result
     * @throws IllegalAccessException
     */
    @SneakyThrows
    public static void decrypt(Field field, Object result) throws IllegalAccessException {
        field.setAccessible(true);
        SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
        if (Objects.nonNull(sensitiveField)) {
            Object object = field.get(result);
            Class<? extends SecretAbstract> secret = sensitiveField.value();
            SecretAbstract secretAbstract = ReflectUtil.newInstance(secret);
            fieldDecrypt(field, result, object, secretAbstract::decrypt);
        }
    }

    public static <T> void fieldDecrypt(Field field, Object result, Object object, Function<String, String> function) throws IllegalAccessException {
        if (object instanceof String) {
            //定制String类型的解密算法
            String value = (String) object;
            String decData = function.apply(value);
            if (StrUtil.isNotEmpty(decData)) {
                field.set(result, decData);
            } else {
                field.set(result, value);
            }
        }
    }

    /**
     * 检查是否敏感表
     * @param result
     * @return
     */
    public static boolean checkIsSensitiveTable(Object result) {
        return result.getClass().getAnnotation(SensitiveTable.class) != null;
    }

}